package com.jprocms.module.cms.framework.config;

import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
import com.jprocms.module.cms.framework.seq.IdBuilder;
import com.jprocms.module.cms.framework.seq.IdSeqAnno;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author jprocms
 */
@Component
public class CustomIdGenerator implements IdentifierGenerator {
    private final ConcurrentHashMap<String, IdBuilder> ID_MAP = new ConcurrentHashMap();
    @Autowired
    @Lazy
    private IdSeqGenerator idGenerator;

    @Override
    public Number nextId(Object entity) {
        Class cls = entity.getClass();
        TableInfo tableInfo = SqlHelper.table(cls);
        Number prikey = (Number) tableInfo.getPropertyValue(entity, tableInfo.getKeyProperty());
        if (prikey != null) {
            return prikey;
        }
        IdSeqAnno idGen = getIdGen(entity);
        String bizTag = bizTag(entity, idGen);
        Integer step = step(entity, idGen);
        Integer init = init(entity, idGen);
        Integer id = null;
        while ((id = getNextId(bizTag)) == null) {
            flushId(cls, bizTag, step, init);
        }
        return id;
    }

    /**
     * 获取指定步长的ID集合列表
     *
     * @param step
     * @return
     */
    public List<Number> listByStep(Object entity, Integer step) {
        Class cls = entity.getClass();
        IdSeqAnno idGen = getIdGen(entity);
        String bizTag = bizTag(entity, idGen);
        Integer init = init(entity, idGen);
        IdBuilder idRange = flushIdNew(cls, bizTag, step, init);
        List<Number> numberList = new ArrayList<>();
        Integer start = idRange.getStart();
        for (int i = 0; i < step; i++) {
            numberList.add(start + i);
        }
        return numberList;
    }

    private Integer init(Object entity, IdSeqAnno idGen) {
        if (idGen != null) {
            return idGen.init();
        }
        return 0;
    }

    private void flushId(Class cls, String bizTag, Integer step, Integer init) {
        synchronized (cls) {
            if (getNextId(bizTag) == null) {
                IdBuilder dto = idGenerator.genarate(bizTag, step, init);
                ID_MAP.put(bizTag, dto);
            }
        }
    }

    /**
     * 获取指定class的对象
     *
     * @param cls
     * @param bizTag
     * @param step
     * @param init
     * @return
     */
    private IdBuilder flushIdNew(Class cls, String bizTag, Integer step, Integer init) {
        IdBuilder dto = null;
        synchronized (cls) {
            dto = idGenerator.genarate(bizTag, step, init);
            ID_MAP.put(bizTag, dto);
        }
        return dto;
    }

    private Integer getNextId(String bizTag) {
        IdBuilder idGenaratDto = ID_MAP.get(bizTag);
        if (idGenaratDto == null) {
            return null;
        }
        try {
            return idGenaratDto.nextId();
        } catch (Exception e) {

        }
        return null;
    }

    private Integer step(Object entity, IdSeqAnno idGen) {

        if (idGen != null) {
            return idGen.step();
        }
        return null;
    }

    private String bizTag(Object entity, IdSeqAnno idGen) {
        String bizTag = null;
        if (idGen != null) {
            bizTag = idGen.bizTag();
        }
        if (!StringUtils.hasText(bizTag)) {
            bizTag = SqlHelper.table(entity.getClass()).getTableName();
        }
        return bizTag;
    }

    private IdSeqAnno getIdGen(Object entity) {
        Class cls = entity.getClass();
        TableInfo tableInfo = SqlHelper.table(cls);
        String primaryKey = tableInfo.getKeyProperty();
        try {
            Field f = cls.getDeclaredField(primaryKey);
            IdSeqAnno[] idGens = f.getAnnotationsByType(IdSeqAnno.class);
            if (idGens != null && idGens.length > 0) {
                return idGens[0];
            }
            return null;
        } catch (NoSuchFieldException e) {
            return null;
        }
    }

}
